{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2ee7422d",
   "metadata": {},
   "source": [
    "# VGGNet\n",
    "VGGNet由牛津大学Visual Geometry Group（VGG）团队于2014年提出，是深度学习发展早期的重要里程碑。其核心目标是解决图像识别任务中模型深度与性能的关系问题。在VGGNet之前，AlexNet通过8层网络在2012年ImageNet竞赛中夺冠，但网络深度和结构设计尚未系统探索。当时主流观点认为，增加网络深度会导致梯度消失和计算复杂度激增，难以训练。如何通过更深的网络提升特征表达能力，同时避免参数爆炸和训练困难？VGGNet通过统一使用小卷积核（3×3）和标准化层结构，成功构建了16~19层的深度网络，验证了“深度提升性能”的假设。\n",
    "\n",
    "其模型有如下特征\n",
    "* 小卷积核堆叠策略： 使用多个3×3卷积核替代大尺寸卷积核（如AlexNet的5×5或7×7）。两个3×3卷积堆叠等效于5×5的感受野，但参数量减少28%，且引入更多非线性激活（ReLU），增强特征学习能力。\n",
    "* 标准化网络架构：将网络划分为5段，每段包含2~3个卷积层和1个最大池化层（2×2，步长2），形成“卷积块”设计模式，简化网络结构并提升可扩展性。\n",
    "\n",
    "VGGNet证明了深度对性能的关键作用，直接启发了ResNet，DenseNet等更深的网络设计。例如，ResNet通过残差连接解决了VGGNet训练极深网络时的梯度问题。VGGNet的特征提取能力被广泛用于迁移学习。\n",
    "\n",
    "VGGNet网络结构如下图所示：\n",
    "\n",
    "![alt text](resources/vgg_arch.png \"Title\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d109dd05",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Use device:  cuda\n"
     ]
    }
   ],
   "source": [
    "# 自动重新加载外部module，使得修改代码之后无需重新import\n",
    "# see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n",
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "import time\n",
    "\n",
    "from hdd.device.utils import get_device\n",
    "from hdd.dataset.imagenette_in_memory import ImagenetteInMemory, get_mean_and_std\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms\n",
    "\n",
    "# 设置训练数据的路径\n",
    "DATA_ROOT = \"~/workspace/hands-dirty-on-dl/dataset\"\n",
    "DEVICE = get_device([\"cuda\", \"cpu\"])\n",
    "TENSORBOARD_ROOT = \"~/workspace/hands-dirty-on-dl/dataset\"\n",
    "print(\"Use device: \", DEVICE)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "315fa211",
   "metadata": {},
   "source": [
    "### 加载Imagenette数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cf01264a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from hdd.data_util.transforms import RandomResize\n",
    "\n",
    "TRAIN_MEAN = [0.4625, 0.4580, 0.4295]\n",
    "TRAIN_STD = [0.2452, 0.2390, 0.2469]\n",
    "train_dataset_transforms = transforms.Compose(\n",
    "    [\n",
    "        RandomResize([256, 296, 384]),  # 随机在三个size中选择一个进行resize\n",
    "        transforms.RandomRotation(10),\n",
    "        transforms.RandomCrop(224),\n",
    "        transforms.RandomHorizontalFlip(),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean=TRAIN_MEAN, std=TRAIN_STD),\n",
    "    ]\n",
    ")\n",
    "val_dataset_transforms = transforms.Compose(\n",
    "    [\n",
    "        transforms.Resize(256),\n",
    "        transforms.CenterCrop(224),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean=TRAIN_MEAN, std=TRAIN_STD),\n",
    "    ]\n",
    ")\n",
    "\n",
    "BATCH_SIZE = 32\n",
    "train_dataloader = torch.utils.data.DataLoader(\n",
    "    ImagenetteInMemory(\n",
    "        root=DATA_ROOT,\n",
    "        split=\"train\",\n",
    "        size=\"full\",\n",
    "        download=True,\n",
    "        transform=train_dataset_transforms,\n",
    "    ),\n",
    "    batch_size=BATCH_SIZE,\n",
    "    shuffle=True,\n",
    "    num_workers=8,\n",
    "    pin_memory=True,\n",
    ")\n",
    "val_dataloader = torch.utils.data.DataLoader(\n",
    "    ImagenetteInMemory(\n",
    "        root=DATA_ROOT,\n",
    "        split=\"val\",\n",
    "        size=\"full\",\n",
    "        download=True,\n",
    "        transform=val_dataset_transforms,\n",
    "    ),\n",
    "    batch_size=BATCH_SIZE,\n",
    "    shuffle=False,\n",
    "    num_workers=8,\n",
    "    pin_memory=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f5cfd0a",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ff6165eb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 1/150 Train Loss: 2.0089 Accuracy: 0.2974 Time: 37.23322  | Val Loss: 1.7045 Accuracy: 0.4201\n",
      "Epoch: 2/150 Train Loss: 1.6748 Accuracy: 0.4314 Time: 37.05103  | Val Loss: 1.5062 Accuracy: 0.5065\n",
      "Epoch: 3/150 Train Loss: 1.4549 Accuracy: 0.5201 Time: 37.16928  | Val Loss: 1.2228 Accuracy: 0.5944\n",
      "Epoch: 4/150 Train Loss: 1.3202 Accuracy: 0.5711 Time: 37.15570  | Val Loss: 1.0789 Accuracy: 0.6499\n",
      "Epoch: 5/150 Train Loss: 1.2176 Accuracy: 0.6096 Time: 35.94672  | Val Loss: 1.0751 Accuracy: 0.6487\n",
      "Epoch: 6/150 Train Loss: 1.1335 Accuracy: 0.6326 Time: 36.24680  | Val Loss: 0.9804 Accuracy: 0.6805\n",
      "Epoch: 7/150 Train Loss: 1.0783 Accuracy: 0.6545 Time: 35.61917  | Val Loss: 1.2549 Accuracy: 0.6275\n",
      "Epoch: 8/150 Train Loss: 1.0210 Accuracy: 0.6760 Time: 36.25680  | Val Loss: 0.8412 Accuracy: 0.7343\n",
      "Epoch: 9/150 Train Loss: 0.9532 Accuracy: 0.6976 Time: 35.60282  | Val Loss: 0.9126 Accuracy: 0.7139\n",
      "Epoch: 10/150 Train Loss: 0.9195 Accuracy: 0.7073 Time: 35.72305  | Val Loss: 0.7800 Accuracy: 0.7521\n",
      "Epoch: 11/150 Train Loss: 0.8706 Accuracy: 0.7199 Time: 35.96791  | Val Loss: 0.7943 Accuracy: 0.7518\n",
      "Epoch: 12/150 Train Loss: 0.8246 Accuracy: 0.7384 Time: 35.27986  | Val Loss: 0.8233 Accuracy: 0.7439\n",
      "Epoch: 13/150 Train Loss: 0.7826 Accuracy: 0.7510 Time: 35.24840  | Val Loss: 0.7555 Accuracy: 0.7679\n",
      "Epoch: 14/150 Train Loss: 0.7493 Accuracy: 0.7558 Time: 36.87502  | Val Loss: 0.8980 Accuracy: 0.7317\n",
      "Epoch: 15/150 Train Loss: 0.7291 Accuracy: 0.7694 Time: 37.06797  | Val Loss: 0.7661 Accuracy: 0.7620\n",
      "Epoch: 16/150 Train Loss: 0.7069 Accuracy: 0.7744 Time: 36.80806  | Val Loss: 0.5686 Accuracy: 0.8138\n",
      "Epoch: 17/150 Train Loss: 0.6829 Accuracy: 0.7811 Time: 37.05859  | Val Loss: 0.7737 Accuracy: 0.7618\n",
      "Epoch: 18/150 Train Loss: 0.6587 Accuracy: 0.7929 Time: 37.25560  | Val Loss: 0.6110 Accuracy: 0.8102\n",
      "Epoch: 19/150 Train Loss: 0.6297 Accuracy: 0.8006 Time: 37.12183  | Val Loss: 0.5666 Accuracy: 0.8194\n",
      "Epoch: 20/150 Train Loss: 0.6218 Accuracy: 0.7999 Time: 37.07510  | Val Loss: 0.5957 Accuracy: 0.8161\n",
      "Epoch: 21/150 Train Loss: 0.5103 Accuracy: 0.8351 Time: 36.90980  | Val Loss: 0.4379 Accuracy: 0.8629\n",
      "Epoch: 22/150 Train Loss: 0.4800 Accuracy: 0.8424 Time: 37.15586  | Val Loss: 0.4658 Accuracy: 0.8527\n",
      "Epoch: 23/150 Train Loss: 0.4693 Accuracy: 0.8491 Time: 36.36676  | Val Loss: 0.4477 Accuracy: 0.8558\n",
      "Epoch: 24/150 Train Loss: 0.4422 Accuracy: 0.8562 Time: 36.09555  | Val Loss: 0.4326 Accuracy: 0.8639\n",
      "Epoch: 25/150 Train Loss: 0.4317 Accuracy: 0.8603 Time: 35.73765  | Val Loss: 0.4167 Accuracy: 0.8698\n",
      "Epoch: 26/150 Train Loss: 0.4248 Accuracy: 0.8596 Time: 35.85596  | Val Loss: 0.4455 Accuracy: 0.8611\n",
      "Epoch: 27/150 Train Loss: 0.4121 Accuracy: 0.8679 Time: 35.82704  | Val Loss: 0.4144 Accuracy: 0.8716\n",
      "Epoch: 28/150 Train Loss: 0.4237 Accuracy: 0.8637 Time: 36.00221  | Val Loss: 0.4411 Accuracy: 0.8622\n",
      "Epoch: 29/150 Train Loss: 0.3930 Accuracy: 0.8693 Time: 36.83639  | Val Loss: 0.4044 Accuracy: 0.8752\n",
      "Epoch: 30/150 Train Loss: 0.4035 Accuracy: 0.8686 Time: 36.89836  | Val Loss: 0.3891 Accuracy: 0.8767\n",
      "Epoch: 31/150 Train Loss: 0.3813 Accuracy: 0.8756 Time: 37.14476  | Val Loss: 0.3890 Accuracy: 0.8805\n",
      "Epoch: 32/150 Train Loss: 0.3705 Accuracy: 0.8741 Time: 36.45423  | Val Loss: 0.4128 Accuracy: 0.8726\n",
      "Epoch: 33/150 Train Loss: 0.3739 Accuracy: 0.8793 Time: 36.56979  | Val Loss: 0.4385 Accuracy: 0.8645\n",
      "Epoch: 34/150 Train Loss: 0.3633 Accuracy: 0.8802 Time: 36.49111  | Val Loss: 0.4085 Accuracy: 0.8690\n",
      "Epoch: 35/150 Train Loss: 0.3686 Accuracy: 0.8797 Time: 36.55229  | Val Loss: 0.4148 Accuracy: 0.8685\n",
      "Epoch: 36/150 Train Loss: 0.3541 Accuracy: 0.8846 Time: 36.68936  | Val Loss: 0.3818 Accuracy: 0.8772\n",
      "Epoch: 37/150 Train Loss: 0.3430 Accuracy: 0.8884 Time: 36.96922  | Val Loss: 0.4035 Accuracy: 0.8805\n",
      "Epoch: 38/150 Train Loss: 0.3383 Accuracy: 0.8895 Time: 36.13203  | Val Loss: 0.3977 Accuracy: 0.8731\n",
      "Epoch: 39/150 Train Loss: 0.3382 Accuracy: 0.8906 Time: 36.86136  | Val Loss: 0.3978 Accuracy: 0.8744\n",
      "Epoch: 40/150 Train Loss: 0.3254 Accuracy: 0.8962 Time: 35.50294  | Val Loss: 0.4163 Accuracy: 0.8675\n",
      "Epoch: 41/150 Train Loss: 0.2640 Accuracy: 0.9141 Time: 35.50476  | Val Loss: 0.3416 Accuracy: 0.8915\n",
      "Epoch: 42/150 Train Loss: 0.2543 Accuracy: 0.9203 Time: 35.12019  | Val Loss: 0.3264 Accuracy: 0.8981\n",
      "Epoch: 43/150 Train Loss: 0.2445 Accuracy: 0.9228 Time: 35.08481  | Val Loss: 0.3519 Accuracy: 0.8938\n",
      "Epoch: 44/150 Train Loss: 0.2444 Accuracy: 0.9205 Time: 35.08108  | Val Loss: 0.3323 Accuracy: 0.9006\n",
      "Epoch: 45/150 Train Loss: 0.2469 Accuracy: 0.9184 Time: 35.06971  | Val Loss: 0.3285 Accuracy: 0.8999\n",
      "Epoch: 46/150 Train Loss: 0.2268 Accuracy: 0.9282 Time: 35.10153  | Val Loss: 0.3172 Accuracy: 0.9034\n",
      "Epoch: 47/150 Train Loss: 0.2369 Accuracy: 0.9241 Time: 35.05405  | Val Loss: 0.3206 Accuracy: 0.9014\n",
      "Epoch: 48/150 Train Loss: 0.2298 Accuracy: 0.9262 Time: 35.14445  | Val Loss: 0.3413 Accuracy: 0.8966\n",
      "Epoch: 49/150 Train Loss: 0.2328 Accuracy: 0.9229 Time: 34.27152  | Val Loss: 0.3121 Accuracy: 0.9062\n",
      "Epoch: 50/150 Train Loss: 0.2293 Accuracy: 0.9263 Time: 36.02108  | Val Loss: 0.3325 Accuracy: 0.9039\n",
      "Epoch: 51/150 Train Loss: 0.2387 Accuracy: 0.9246 Time: 37.30443  | Val Loss: 0.3444 Accuracy: 0.8994\n",
      "Epoch: 52/150 Train Loss: 0.2193 Accuracy: 0.9284 Time: 36.96240  | Val Loss: 0.3387 Accuracy: 0.9009\n",
      "Epoch: 53/150 Train Loss: 0.2225 Accuracy: 0.9255 Time: 36.91360  | Val Loss: 0.3312 Accuracy: 0.9070\n",
      "Epoch: 54/150 Train Loss: 0.2178 Accuracy: 0.9307 Time: 37.12508  | Val Loss: 0.3070 Accuracy: 0.9090\n",
      "Epoch: 55/150 Train Loss: 0.2001 Accuracy: 0.9341 Time: 37.05161  | Val Loss: 0.3467 Accuracy: 0.9001\n",
      "Epoch: 56/150 Train Loss: 0.2216 Accuracy: 0.9282 Time: 37.23685  | Val Loss: 0.3362 Accuracy: 0.9017\n",
      "Epoch: 57/150 Train Loss: 0.2086 Accuracy: 0.9329 Time: 37.11144  | Val Loss: 0.3286 Accuracy: 0.9045\n",
      "Epoch: 58/150 Train Loss: 0.2087 Accuracy: 0.9312 Time: 36.77072  | Val Loss: 0.3205 Accuracy: 0.9006\n",
      "Epoch: 59/150 Train Loss: 0.2013 Accuracy: 0.9332 Time: 36.66645  | Val Loss: 0.3539 Accuracy: 0.8961\n",
      "Epoch: 60/150 Train Loss: 0.2050 Accuracy: 0.9307 Time: 36.80890  | Val Loss: 0.3251 Accuracy: 0.9022\n",
      "Epoch: 61/150 Train Loss: 0.1810 Accuracy: 0.9406 Time: 36.49585  | Val Loss: 0.2949 Accuracy: 0.9129\n",
      "Epoch: 62/150 Train Loss: 0.1673 Accuracy: 0.9466 Time: 37.02718  | Val Loss: 0.3143 Accuracy: 0.9073\n",
      "Epoch: 63/150 Train Loss: 0.1711 Accuracy: 0.9443 Time: 36.48442  | Val Loss: 0.3117 Accuracy: 0.9088\n",
      "Epoch: 64/150 Train Loss: 0.1643 Accuracy: 0.9460 Time: 36.96707  | Val Loss: 0.3222 Accuracy: 0.9057\n",
      "Epoch: 65/150 Train Loss: 0.1604 Accuracy: 0.9472 Time: 37.24285  | Val Loss: 0.3090 Accuracy: 0.9093\n",
      "Epoch: 66/150 Train Loss: 0.1607 Accuracy: 0.9505 Time: 36.82904  | Val Loss: 0.3034 Accuracy: 0.9118\n",
      "Epoch: 67/150 Train Loss: 0.1550 Accuracy: 0.9484 Time: 36.57327  | Val Loss: 0.2936 Accuracy: 0.9157\n",
      "Epoch: 68/150 Train Loss: 0.1586 Accuracy: 0.9506 Time: 36.83151  | Val Loss: 0.3257 Accuracy: 0.9065\n",
      "Epoch: 69/150 Train Loss: 0.1491 Accuracy: 0.9494 Time: 37.60692  | Val Loss: 0.3053 Accuracy: 0.9146\n",
      "Epoch: 70/150 Train Loss: 0.1483 Accuracy: 0.9533 Time: 37.53299  | Val Loss: 0.3069 Accuracy: 0.9152\n",
      "Epoch: 71/150 Train Loss: 0.1555 Accuracy: 0.9492 Time: 36.52193  | Val Loss: 0.3118 Accuracy: 0.9098\n",
      "Epoch: 72/150 Train Loss: 0.1588 Accuracy: 0.9486 Time: 36.77004  | Val Loss: 0.3228 Accuracy: 0.9080\n",
      "Epoch: 73/150 Train Loss: 0.1550 Accuracy: 0.9509 Time: 36.72046  | Val Loss: 0.3065 Accuracy: 0.9083\n",
      "Epoch: 74/150 Train Loss: 0.1362 Accuracy: 0.9560 Time: 36.59586  | Val Loss: 0.3044 Accuracy: 0.9129\n",
      "Epoch: 75/150 Train Loss: 0.1499 Accuracy: 0.9500 Time: 36.84380  | Val Loss: 0.3011 Accuracy: 0.9136\n",
      "Epoch: 76/150 Train Loss: 0.1443 Accuracy: 0.9540 Time: 37.22024  | Val Loss: 0.3369 Accuracy: 0.9006\n",
      "Epoch: 77/150 Train Loss: 0.1475 Accuracy: 0.9529 Time: 36.93540  | Val Loss: 0.3174 Accuracy: 0.9106\n",
      "Epoch: 78/150 Train Loss: 0.1413 Accuracy: 0.9551 Time: 36.62887  | Val Loss: 0.3177 Accuracy: 0.9090\n",
      "Epoch: 79/150 Train Loss: 0.1373 Accuracy: 0.9573 Time: 36.73571  | Val Loss: 0.3068 Accuracy: 0.9129\n",
      "Epoch: 80/150 Train Loss: 0.1417 Accuracy: 0.9552 Time: 36.75044  | Val Loss: 0.3158 Accuracy: 0.9136\n",
      "Epoch: 81/150 Train Loss: 0.1371 Accuracy: 0.9575 Time: 36.98494  | Val Loss: 0.3226 Accuracy: 0.9085\n",
      "Epoch: 82/150 Train Loss: 0.1217 Accuracy: 0.9611 Time: 36.44647  | Val Loss: 0.3072 Accuracy: 0.9131\n",
      "Epoch: 83/150 Train Loss: 0.1170 Accuracy: 0.9642 Time: 36.97045  | Val Loss: 0.3039 Accuracy: 0.9113\n",
      "Epoch: 84/150 Train Loss: 0.1239 Accuracy: 0.9583 Time: 36.83914  | Val Loss: 0.3146 Accuracy: 0.9134\n",
      "Epoch: 85/150 Train Loss: 0.1241 Accuracy: 0.9619 Time: 36.53358  | Val Loss: 0.3082 Accuracy: 0.9113\n",
      "Epoch: 86/150 Train Loss: 0.1081 Accuracy: 0.9649 Time: 36.67447  | Val Loss: 0.3107 Accuracy: 0.9164\n",
      "Epoch: 87/150 Train Loss: 0.1085 Accuracy: 0.9649 Time: 36.82315  | Val Loss: 0.3098 Accuracy: 0.9149\n",
      "Epoch: 88/150 Train Loss: 0.1200 Accuracy: 0.9629 Time: 36.86724  | Val Loss: 0.3046 Accuracy: 0.9141\n",
      "Epoch: 89/150 Train Loss: 0.1191 Accuracy: 0.9619 Time: 36.42210  | Val Loss: 0.3167 Accuracy: 0.9108\n",
      "Epoch: 90/150 Train Loss: 0.1090 Accuracy: 0.9654 Time: 36.22774  | Val Loss: 0.2970 Accuracy: 0.9167\n",
      "Epoch: 91/150 Train Loss: 0.1174 Accuracy: 0.9639 Time: 36.21069  | Val Loss: 0.3240 Accuracy: 0.9144\n",
      "Epoch: 92/150 Train Loss: 0.1132 Accuracy: 0.9641 Time: 36.36921  | Val Loss: 0.3109 Accuracy: 0.9162\n",
      "Early stop at epoch 92!\n"
     ]
    }
   ],
   "source": [
    "from hdd.models.cnn.vggnet import VGGNet, cfgs\n",
    "from hdd.train.early_stopping import EarlyStoppingInMem\n",
    "from hdd.train.classification_utils import naive_train_classification_model\n",
    "\n",
    "\n",
    "net = VGGNet(cfgs[\"E\"], num_classes=10, dropout=0.2).to(DEVICE)\n",
    "criteria = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.SGD(net.parameters(), lr=0.005, momentum=0.9)\n",
    "scheduler = torch.optim.lr_scheduler.StepLR(\n",
    "    optimizer, step_size=20, gamma=0.5, last_epoch=-1\n",
    ")\n",
    "early_stopper = EarlyStoppingInMem(patience=25, verbose=False)\n",
    "max_epochs = 150\n",
    "_ = naive_train_classification_model(\n",
    "    net,\n",
    "    criteria,\n",
    "    max_epochs,\n",
    "    train_dataloader,\n",
    "    val_dataloader,\n",
    "    DEVICE,\n",
    "    optimizer,\n",
    "    scheduler,\n",
    "    early_stopper,\n",
    "    verbose=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1a3ad181",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.9156687898089172\n"
     ]
    }
   ],
   "source": [
    "from hdd.train.classification_utils import eval_image_classifier\n",
    "\n",
    "eval_result = eval_image_classifier(net, val_dataloader.dataset, DEVICE)\n",
    "ss = [result.gt_label == result.predicted_label for result in eval_result]\n",
    "print(f\"Accuracy: {sum(ss) / len(ss)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfa4ab16",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "05fd28d0",
   "metadata": {},
   "source": [
    "在该**小**数据集上，我们分别测试了VGG A,B,D,E网络的性能\n",
    "| VGG网络结构 | Val Accuracy | Learning Rate |\n",
    "| :---:   | :---: | :---: | \n",
    "| A | 90.29% | 0.01 |\n",
    "| B | 91.16%|  0.01 | \n",
    "| D | 91.49%| 0.005 |\n",
    "| E | 91.57%| 0.005 | "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37c4954f",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch-cu124",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
